// Console input and output.
// Input is from the keyboard or serial port.
// Output is written to the screen and serial port.

#include "types.h"
#include "defs.h"
#include "param.h"
#include "traps.h"
#include "spinlock.h"
#include "fs.h"
#include "file.h"
#include "mmu.h"
#include "proc.h"
#include "x86.h"

#define HISTORY_SIZE 16


static void consputc(int);

static int panicked = 0;

static struct {
	struct spinlock lock;
	int locking;
} cons;

static void
printint(int xx, int base, int sgn)
{
	static char digits[] = "0123456789abcdef";
	char buf[16];
	int i = 0, neg = 0;
	uint x;

	if(sgn && xx < 0){
		neg = 1;
		x = -xx;
	} else
		x = xx;

	do{
		buf[i++] = digits[x % base];
	}while((x /= base) != 0);
	if(neg)
		buf[i++] = '-';

	while(--i >= 0)
		consputc(buf[i]);
}

// Print to the console. only understands %d, %x, %p, %s.
void
cprintf(char *fmt, ...)
{
	int i, c, state, locking;
	uint *argp;
	char *s;

	locking = cons.locking;
	if(locking)
		acquire(&cons.lock);

	argp = (uint*)(void*)(&fmt + 1);
	state = 0;
	for(i = 0; (c = fmt[i] & 0xff) != 0; i++){
		if(c != '%'){
			consputc(c);
			continue;
		}
		c = fmt[++i] & 0xff;
		if(c == 0)
			break;
		switch(c){
		case 'd':
			printint(*argp++, 10, 1);
			break;
		case 'x':
		case 'p':
			printint(*argp++, 16, 0);
			break;
		case 's':
			if((s = (char*)*argp++) == 0)
				s = "(null)";
			for(; *s; s++)
				consputc(*s);
			break;
		case '%':
			consputc('%');
			break;
		default:
			// Print unknown % sequence to draw attention.
			consputc('%');
			consputc(c);
			break;
		}
	}

	if(locking)
		release(&cons.lock);
}

void
panic(char *s)
{
	int i;
	uint pcs[10];

	cli();
	cons.locking = 0;
	cprintf("cpu%d: panic: ", cpu->id);
	cprintf(s);
	cprintf("\n");
	getcallerpcs(&s, pcs);
	for(i=0; i<10; i++)
		cprintf(" %p", pcs[i]);
	panicked = 1; // freeze other CPU
	for(;;)
		;
}

#define BACKSPACE 0x100
#define CRTPORT 0x3d4
static ushort *crt = (ushort*)0xb8000;  // CGA memory

static void
cgaputc(int c)
{
	int pos;

	// Cursor position: col + 80*row.
	outb(CRTPORT, 14);
	pos = inb(CRTPORT+1) << 8;
	outb(CRTPORT, 15);
	pos |= inb(CRTPORT+1);

	if(c == '\n')
		pos += 80 - pos%80;
	else if(c == BACKSPACE){
		if(pos > 0) --pos;
	} else
		crt[pos++] = (c&0xff) | 0x0700;  // black on white

	if((pos/80) >= 24){  // Scroll up.
		memmove(crt, crt+80, sizeof(crt[0])*23*80);
		pos -= 80;
		memset(crt+pos, 0, sizeof(crt[0])*(24*80 - pos));
	}

	outb(CRTPORT, 14);
	outb(CRTPORT+1, pos>>8);
	outb(CRTPORT, 15);
	outb(CRTPORT+1, pos);
	crt[pos] = ' ' | 0x0700;
}

void
consputc(int c)
{
	if(panicked){
		cli();
		for(;;)
			;
	}

	if(c == BACKSPACE){
		uartputc('\b'); uartputc(' '); uartputc('\b');
	} else
		uartputc(c);
	cgaputc(c);
}


#define INPUT_BUF 128
struct {
	struct spinlock lock;
	char buf[INPUT_BUF];
	uint r;  // Read index
	uint w;  // Write index
	uint e;  // Edit index
} input;

#define C(x)  ((x)-'@')  // Control-x
struct {
	char buf[INPUT_BUF * HISTORY_SIZE];
	uint total;
	uint r;
	uint w;
} history;

void
copyLastCmd(){
	int start;
	int end;
	int t=0;

	//cprintf("!%d\n",input.e);
	if (input.e <= 1)// || input.buf[(input.w-2) % INPUT_BUF] == '\n')
		return;

	start=(input.e-2)% INPUT_BUF; // last char before '\n' of last command
	end = (input.e-1)% INPUT_BUF; // '\n' of last command
	while (input.buf[start] != '\n' && start > 0)
		start--;

	if (start)
		t=1;

	safestrcpy(history.buf+(history.w*INPUT_BUF), input.buf+start+t, end-start+(1-t));
	history.r = history.w;
	history.w = (history.w++) % HISTORY_SIZE;
	if (history.total < HISTORY_SIZE){
		history.total++;
	}


}

#define UP_ARROW    226

void
consoleintr(int (*getc)(void))
{
	int c;
	uint i;
	int scancode=0;

	acquire(&input.lock);
	while((c = getc()) >= 0){
		switch(c){
		case 0:
			scancode=1;
			continue;
		case C('P'):  // Process listing.
    		procdump();
		break;
		case C('U'):  // Kill line.
    		while(input.e != input.w &&
    		  input.buf[(input.e-1) % INPUT_BUF] != '\n'){
    		    input.e--;
    			consputc(BACKSPACE);
    		}
			break;
		case C('H'): case '\x7f':  // Backspace
			if(input.e != input.w){
				input.e--;
				consputc(BACKSPACE);
			}
			break;
		case C('I'):  // Up arrow OR Control+I. currently deletes the line. code's just a place holder.
    		while(input.e != input.w &&
    		  input.buf[(input.e-1) % INPUT_BUF] != '\n'){
    		  input.e--;
    		  consputc(BACKSPACE);
    		}
			if (history.total){
				i = history.r * INPUT_BUF;
				while(history.buf[i]){
					input.buf[input.e++ % INPUT_BUF] = history.buf[i];
					consputc((int)history.buf[i]);
					++i;
				}
			history.r = (history.r + (history.total -1)) % history.total; // my special go one back
			}
			break;
		case UP_ARROW:
			if (scancode){
				while(input.e != input.w &&
						input.buf[(input.e-1) % INPUT_BUF] != '\n'){
					input.e--;
					consputc(BACKSPACE);
				}
				if (history.total){
					i = history.r * INPUT_BUF;
					while(history.buf[i]){
						input.buf[input.e++ % INPUT_BUF] = history.buf[i];
						consputc((int)history.buf[i]);
						++i;
					}
					history.r = (history.r + (history.total -1)) % history.total; // my special go one back
				}
			}
			break;
		default:
			if(c != 0 && input.e-input.r < INPUT_BUF){
				c = (c == '\r') ? '\n' : c;
				input.buf[input.e++ % INPUT_BUF] = c;
				consputc(c);
				if(c == '\n' || c == C('D') || input.e == input.r+INPUT_BUF){
					copyLastCmd();
					input.w = input.e;
					wakeup(&input.r);
				}
			}
			break;
		}
	}
	release(&input.lock);
}

int
consoleread(struct inode *ip, char *dst, int n)
{
	uint target;
	int c;

	iunlock(ip);
	target = n;
	acquire(&input.lock);
	while(n > 0){
		while(input.r == input.w){
			if(proc->killed){
				release(&input.lock);
				ilock(ip);
				return -1;
			}
			sleep(&input.r, &input.lock);
		}
		c = input.buf[input.r++ % INPUT_BUF];
		if(c == C('D')){  // EOF
			if(n < target){
				// Save ^D for next time, to make sure
				// caller gets a 0-byte result.
				input.r--;
			}
			break;
		}
		*dst++ = c;
		--n;
		if(c == '\n')
			break;
	}
	release(&input.lock);
	ilock(ip);

	return target - n;
}

int
consolewrite(struct inode *ip, char *buf, int n)
{
	int i;

	iunlock(ip);
	acquire(&cons.lock);
	for(i = 0; i < n; i++)
		consputc(buf[i] & 0xff);
	release(&cons.lock);
	ilock(ip);

	return n;
}

void
consoleinit(void)
{
	initlock(&cons.lock, "console");
	initlock(&input.lock, "input");

	devsw[CONSOLE].write = consolewrite;
	devsw[CONSOLE].read = consoleread;
	cons.locking = 1;

	picenable(IRQ_KBD);
	ioapicenable(IRQ_KBD, 0);
}

